﻿using Ndns.Packet.Helpers;
using Ndns.Packet.Text;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Ndns.Packet
{
    public class DnsHostName : IReadOnlyList<string>, IEquatable<DnsHostName>
    {
        private static PunycodeEncoding Punycode => PunycodeEncoding.Default;

        private List<string> _domainParts;

        #region 构造DnsHostName实例

        public DnsHostName()
        {
            _domainParts = new List<string>();
        }

        private DnsHostName(int capacity)
        {
            _domainParts = new List<capacity>();
        }

        public DnsHostName(IEnumerable<string> domainParts)
        {
            if (domainParts == null)
                throw new ArgumentNullException(nameof(domainParts));

            if (domainParts.Any(p => string.IsNullOrWhiteSpace(p) || p.Contains(".") || p.Contains("。")))
                throw new FormatException(nameof(domainParts));
            _domainParts = domainParts.ToList();
        }

        /// <summary>
        /// 利用给定的域名构造一个DnsHostName实例。
        /// </summary>
        /// <param name="hostName">给定的域名。</param>
        public DnsHostName(string hostName)
        {
            if (hostName == null)
                throw new ArgumentNullException(nameof(hostName));

            _domainParts = new List<string>();
            hostName = hostName.Replace('。', '.');
            if (hostName.EndsWith(".")) hostName = hostName.Substring(0, hostName.Length - 1);
            foreach (string eachPart in hostName.Split('.'))
            {
                _domainParts.Add(Punycode.DecodeDomainPart(eachPart));
            }
        }

        public static explicit operator DnsHostName(string hostName)
        {
            return new DnsHostName(hostName);
        }
        public static explicit operator string(DnsHostName hostName)
        {
            return hostName.ToString();
        }

        /// <summary>
        /// 从包含数据包的流中以支持指针的方式构造DnsHostName实例。
        /// </summary>
        /// <param name="packetStream">数据包流，Position应该指向要解析的域名的开始。</param>
        /// <param name="packetStartIndex">数据包的开始位置在流中的偏移量。</param>
        /// <returns>解析得到的DnsHostName实例。</returns>
        public static DnsHostName FromPacket(Stream packetStream, long packetStartIndex)
        {
            if (packetStream == null)
                throw new ArgumentNullException(nameof(packetStream));
            if (packetStartIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(packetStartIndex));

            var hostName = new DnsHostName();
            var buffer = new byte[64];

            //记录数据包开始位置。
            long hostNameStartIndex = packetStream.Position;
            //域名数据结束位置。
            long endIndex = hostNameStartIndex;

            using (var bufferStream = new MemoryStream())
            {
                do
                {
                    bufferStream.Seek(0, SeekOrigin.Begin);
                    //获取长度
                    int len = packetStream.ReadByte();
                    PacketEndOfStreamException.CheckValue(len);
                    byte bLen = (byte)len;
                    //达到末尾时退出
                    if (bLen == 0)
                    {
                        if (endIndex == hostNameStartIndex)
                            endIndex = packetStream.Position;
                        break;
                    }
                    //遇到指针
                    if (DnsHostNamePointer.IsPointer(bLen))
                    {
                        //计算指针指向的位置
                        int pot = packetStream.ReadByte();
                        PacketEndOfStreamException.CheckValue(pot);
                        var pointer = DnsHostNamePointer.Create(bLen, (byte)pot);
                        //保存现在的指针位置
                        if (endIndex == hostNameStartIndex)
                            endIndex = packetStream.Position;
                        //跳转到对应位置
                        packetStream.Seek(packetStartIndex + pointer.Position, SeekOrigin.Begin);
                        //继续解析
                        continue;
                    }
                    //读取内容
                    len = packetStream.CopyBytesTo(bufferStream, bLen, buffer);
                    if (len != bLen)
                        PacketEndOfStreamException.CheckValue(-1);
                    //转换成字符
                    hostName._domainParts.Add(Punycode.DecodeDomainPart(bufferStream.GetBuffer(0, len)));
                } while (true);
            }

            packetStream.Seek(endIndex, SeekOrigin.Begin);
            return hostName;
        }
        #endregion

        #region 获取相关信息

        /// <summary>
        /// 获取字节数组表示的DnsHostName实例。
        /// </summary>
        public byte[] GetBytes()
        {
            using (var ms = new MemoryStream())
            {
                this.CopyTo(ms);
                return ms.ToArray();
            }
        }

        /// <summary>
        /// 将此实例表示的域名写入到流中。
        /// </summary>
        /// <returns>向流中写入的字节数</returns>
        public int CopyTo(Stream s)
        {
            if (s == null)
                throw new ArgumentNullException(nameof(s));

            int count = 1;
            foreach (string part in _domainParts)
            {
                byte[] encodedBytes = Punycode.EncodeDomainPartToArray(part.ToLowerInvariant());
                s.WriteByte((byte)encodedBytes.Length);
                s.Write(encodedBytes);
                count += encodedBytes.Length + 1;
            }
            s.WriteByte(0);
            return count;
        }

        public int CopyCompressedTo(Stream s, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            if (s == null)
                throw new ArgumentNullException(nameof(s));
            if (symbols == null)
                throw new ArgumentNullException(nameof(symbols));

            int originalOffset = offset;
            var buffer = new StringBuilder();

            for (int i = 0; i < _domainParts.Count; i++)
            {
                var remainingPart = ConvertDomainPartsToString(i, buffer);
                if (symbols.ContainsKey(remainingPart))
                {
                    //加入指针
                    var pointer = symbols[remainingPart];
                    pointer.WriteTo(s);
                    offset += 2;
                    //退出
                    return offset - originalOffset;
                }
                else
                {
                    //在符号字典中记录
                    symbols.Add(remainingPart, (DnsHostNamePointer)offset);
                    //加入流
                    byte[] encodedBytes = Punycode.EncodeDomainPartToArray(_domainParts[i].ToLowerInvariant());
                    s.WriteByte((byte)encodedBytes.Length);
                    s.Write(encodedBytes);
                    offset += 1 * sizeof(byte) + encodedBytes.Length;
                }
            }
            //加入末尾0
            s.WriteByte(0);
            offset++;

            return offset - originalOffset;
        }

        private string ConvertDomainPartsToString(int start_index, StringBuilder buffer)
        {
            buffer.Clear();
            for (int i = start_index; i < _domainParts.Count; i++)
            {
                buffer.Append(_domainParts[i].ToLowerInvariant());
                buffer.Append('.');
            }
            return buffer.ToString();
        }

        /// <summary>
        /// 返回此实例表示的域名Punycode解码后的文字。（最后包括一个点）
        /// </summary>
        public override string ToString()
        {
            return string.Join(".", this) + ".";
        }

        #endregion

        #region +运算符实现

        public static DnsHostName operator+(DnsHostName a, DnsHostName b)
        {
            DnsHostName hostName = new DnsHostName(a.Count + b.Count);
            var domainParts = hostName._domainParts;
            domainParts.AddRange(a._domainParts);
            domainParts.AddRange(b._domainParts);
            return hostName;
        }

        #endregion

        #region Clone,Equals,HashCode实现

        internal DnsHostName Clone()
        {
            return new DnsHostName(_domainParts);
        }

        public override bool Equals(Object obj)
        {
            if (this == null)                        //this is necessary to guard against reverse-pinvokes and
                throw new NullReferenceException();  //other callers who do not use the callvirt instruction

            DnsHostName hn = obj as DnsHostName;
            if ((object)hn == null)
                return false;

            if (ReferenceEquals(this, obj))
                return true;
            
            if (_domainParts.Count != hn._domainParts.Count)
                return false;

            return EqualsHelper(this, hn);
        }

        public bool Equals(DnsHostName other)
        {
            if (this == null)                        //this is necessary to guard against reverse-pinvokes and
                throw new NullReferenceException();  //other callers who do not use the callvirt instruction

            if ((object)other == null)
                return false;

            if (ReferenceEquals(this, other))
                return true;
            
            if (_domainParts.Count != other._domainParts.Count)
                return false;

            return EqualsHelper(this, other);
        }

        public static bool Equals(DnsHostName a, DnsHostName b)
        {
            if ((Object)a == (Object)b)
            {
                return true;
            }

            if ((Object)a == null || (Object)b == null)
            {
                return false;
            }
            
            if (a._domainParts.Count != b._domainParts.Count)
                return false;

            return EqualsHelper(a, b);
        }

        public static bool operator ==(DnsHostName a, DnsHostName b)
        {
            return Equals(a, b);
        }

        public static bool operator !=(DnsHostName a, DnsHostName b)
        {
            return !Equals(a, b);
        }

        protected static bool EqualsHelper(DnsHostName a, DnsHostName b)
        {
            IEnumerator<string> ea = a._domainParts.GetEnumerator();
            IEnumerator<string> eb = b._domainParts.GetEnumerator();

            for (; ea.MoveNext() && eb.MoveNext();)
            {
                if (string.Compare(ea.Current, eb.Current, true) != 0)
                    return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            int total = 17;
            foreach (var part in this._domainParts)
            {
                total = total * 37 + part.ToLowerInvariant().GetHashCode();
            }
            return total;
        }

        #endregion

        #region IEnumerable<string>实现

        public IEnumerator<string> GetEnumerator()
        {
            return _domainParts.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _domainParts.GetEnumerator();
        }

        #endregion

        #region IReadOnlyList<string>实现

        public int Count => _domainParts.Count;

        public string this[int index] => _domainParts[index];

        #endregion

    }


}
